const ModbusRTU = require('modbus-serial')
const EventEmitter = require('events')
import {getConfig} from '@/mapper/config-store'

export const plcEvents = new EventEmitter()

let client = new ModbusRTU()
//是否连接
let isConnectionOpen = false
//是否启动
let isStart = true
//状态监听间隔 默认15秒
let statusListeningInterval = 15000
//基础地址集合
let baseAddressList = [
    {
        key: 'station1_scanData',
        address: '8100',
        type: 'string',
        length: 30,
        description: '工位1扫码枪数据'
    }
]

let baseAddressMap = {
    '8100': {
        key: 'station1_scanData',
        address: '8100',
        type: 'string',
        length: 30,
        description: '工位1扫码枪数据'
    }
}

let listenAddressList = [
    {
        startAddress: 8000,
        length: 100
    },
    {
        startAddress: 8100,
        length: 100
    },
    {
        startAddress: 8200,
        length: 100
    }
]

let lastValues = {
    station1_scanData: '',
    station1_scanUpload: 0
}


//地址集合
let ADDRESS_INFO_MAP = {
    station1_scanData: {
        type: 'string',
        length: 30,
        description: '工位1条码数据'
    },
    station1_scanUpload: {
        type: 'uint16',
        length: 2,
        description: '工位1扫码信号'
    },
    station1_mesJudgment: {
        type: 'uint16',
        length: 2,
        description: '工位1扫码结果'
    },
    station1_processComplete: {
        type: 'uint16',
        length: 2,
        description: '工位1生产完成信号'
    },
    station1_processResult: {
        type: 'uint16',
        length: 2,
        description: '工位1生产完成结果'
    },
    station1_dataTrigger: {
        type: 'uint16',
        length: 2,
        description: '工位1数据判断触发'
    },
    station1_duplicateCheck: {
        type: 'uint16',
        length: 2,
        description: '工位1重码判断'
    },
    station2_scanData: {
        type: 'string',
        length: 30,
        description: '工位2条码数据'
    },
    station2_scanUpload: {
        type: 'uint16',
        length: 2,
        description: '工位2扫码信号'
    },
    station2_mesJudgment: {
        type: 'uint16',
        length: 2,
        description: '工位2扫码结果'
    },
    station2_processComplete: {
        type: 'uint16',
        length: 2,
        description: '工位2生产完成信号'
    },
    station2_processResult: {
        type: 'uint16',
        length: 2,
        description: '工位2生产完成结果'
    },
    station2_dataTrigger: {
        type: 'uint16',
        length: 2,
        description: '工位2数据判断触发'
    },
    station2_duplicateCheck: {
        type: 'uint16',
        length: 2,
        description: '工位2重码判断'
    }
}


//数据采集间隔
const delay = 100

async function connectTCP() {
    try {
        let base_config = await getConfig('plcConfig')
        console.log(`正在尝试连接PLC ${base_config.ip}:${base_config.port}...`)
        await client.connectTCP(base_config.ip, {port: base_config.port})
        updateConnectionStatus(true)
        console.log('PLC连接成功!')
    } catch (e) {
        // console.log(e)
    }
}

async function highSpeedPolling() {
    while (isStart) {
        try {
            await new Promise(resolve => setTimeout(resolve, delay))
            if (isConnectionOpen) {
                for (let i = 0; i < listenAddressList.length; i++) {
                    let curr = listenAddressList[i]
                    let values = await client.readHoldingRegisters(curr.startAddress, curr.length)
                    parseValues(curr, values)
                }
            }
        } catch (e) {
            console.error('数据采集报错:', e.message)
        }
    }
}

async function parseValues(curr, values) {
    let startAddress = curr.startAddress
    let data = values.data
    baseAddressList.forEach(address => {
        let startIndex = address.address - startAddress
        let length = address.length
        //地址批次处理
        if (startIndex < 0 || startIndex + length > data.length) {
            return
        }
        let value = data.slice(startIndex, startIndex + length)

        if (address.type === 'string') {
            value = valueToString(value)
        } else if (address.type === 'float') {
            value = valueToFloat(value)
        } else if (address.type === 'int') {
            value = valueToInt(value)
        } else {
            value = value[0]
        }
        if (lastValues[address.key] === undefined) {
            lastValues[address.key] = value
            return
        }
        handleValueChange(address, value, lastValues, baseAddressList)
    })
}

/**
 * 处理PLC地址值变化并触发相应事件
 * @param {Object} address PLC地址对象
 * @param {*} value 当前值
 * @param {Object} lastValues 上次值的缓存对象
 * @param {Array} baseAddressList 基础地址列表
 * @returns {boolean} 值是否发生变化
 */
function handleValueChange(address, value, lastValues, baseAddressList) {
    if (lastValues[address.key] !== value) {
        console.log(`${currTime()}[PLC 监控] 检测到地址 ${address.address} 值变化：${lastValues[address.key]} -> ${value}`)
        lastValues[address.key] = value

        if (address.key.includes('dc_')) {
            plcEvents.emit('dc-value-changed', {
                key: address.key,
                value: value,
                values: lastValues,
                addresses: baseAddressList
            })
        } else {
            plcEvents.emit('value-changed', {
                key: address.key,
                value: value,
                values: lastValues,
                addresses: baseAddressList
            })
        }
    }
}


function valueToString(data) {
    try {
        return data.map(item => {
            const hexString = item.toString(16).padStart(4, '0')
            const firstChar = String.fromCharCode(parseInt(hexString.substring(2, 4), 16))
            const secondChar = String.fromCharCode(parseInt(hexString.substring(0, 2), 16))
            return firstChar + secondChar
        }).join('')
            .replace(/\u0000/g, '')         // 去掉空字符
            .replace(/[\r\n\t\f\b]/g, '')   // 去掉换行、回车、制表等控制符
    } catch (e) {
        console.log('valueToString 错误:', data)
        return ''
    }
}

/**
 * 将PLC读取的数据数组转换为整数
 * @param {Array} data - 从PLC读取的数据数组
 * @returns {number} - 转换后的整数值
 */
function valueToInt(registers) {
    try {
        const low = registers[0];
        const high = registers[1];
        const value = (high << 16) | low;
        return value >> 0;
    } catch (e) {
        console.log('valueToInt 错误:', data, e)
        return 0
    }
}

/**
 * 将PLC读取的数据数组转换为浮点数
 * @param {Array} data - 从PLC读取的数据数组
 * @returns {number} - 转换后的浮点数值
 */
function valueToFloat(registers) {
    try {
        const buffer = new ArrayBuffer(4);
        const view = new DataView(buffer);
        view.setUint16(0, registers[0], true); // 大端序
        view.setUint16(2, registers[1], true); // 大端序
        let value = view.getFloat32(0, true); // 大端序
        return Number(value.toFixed(2));
    } catch (e) {
        console.log('valueToFloat 错误:', data, e)
        return 0.0
    }
}

//连接状态监听
async function monitorConnectionStatus() {
    while (isStart) {
        try {
            await new Promise(resolve => setTimeout(resolve, 1000))
            if (!client.isOpen) {
                updateConnectionStatus(false)
                await connectTCP()
            }
        } catch (e) {
            console.error('监听连接状态报错:', e.message)
        }
    }
}

//设备状态监听
async function monitorDeviceStatus() {
    while (isStart) {
        try {
            await new Promise(resolve => setTimeout(resolve, statusListeningInterval))
            sendDeviceStatusToMes(isConnectionOpen)
        } catch (e) {
            console.error('监听设备状态报错:', e.message)
        }
    }
}

function updateConnectionStatus(status) {
    if (status === isConnectionOpen) {
        return
    }
    console.log('-----------------------------------------', status)
    isConnectionOpen = status
    sendDeviceStatusToMes(isConnectionOpen)
}

async function sendDeviceStatusToMes(isConnectionOpen) {
    try {
        plcEvents.emit('device-status', {
            isConnection: isConnectionOpen
        })
    } catch (e) {
        console.log('发送设备状态报错:', e.message)
    }
}

async function initAddress() {
    //初始化基础业务地址
    baseAddressList = []
    baseAddressMap = {}
    lastValues = {}
    await initBaseAddress()
    await initPlcAddress()
    //从大到小排序
    baseAddressList.sort((a, b) => a.address - b.address)
    console.log('baseAddressList: ', baseAddressList)
    console.log('baseAddressMap: ', baseAddressMap)
    console.log('lastValues: ', lastValues)
    //合并地址
    await mergeAddress()
    console.log('listenAddressList: ', listenAddressList)
}

async function mergeAddress() {
    listenAddressList = []
    if (!baseAddressList || baseAddressList.length === 0) {
        console.log('baseAddressList is empty.')
        console.log('listenAddressList: ', listenAddressList)
        return // 直接返回空列表
    }
    let currentStartAddress = baseAddressList[0].address
    let currentLength = baseAddressList[0].length

    for (let i = 1; i < baseAddressList.length; i++) {
        const nextBlock = baseAddressList[i] // 当前正在考虑的下一个原始地址块
        const currentEndAddress = currentStartAddress + currentLength // 当前正在构建的合并块的结束地址 (不包含结束地址本身，例如长度为5的块从100开始，结束地址是105)
        const gap = nextBlock.address - currentEndAddress
        const potentialNewLength = currentLength + nextBlock.length + Math.abs(gap)
        if (gap <= 10 && potentialNewLength <= 100) {
            currentLength = potentialNewLength
        } else {
            listenAddressList.push({
                startAddress: currentStartAddress,
                length: currentLength
            })
            currentStartAddress = nextBlock.address
            currentLength = nextBlock.length
        }
    }
    listenAddressList.push({
        startAddress: currentStartAddress,
        length: currentLength
    })
}

async function initPlcAddress() {
    let addresses = await getConfig('plcAddresses')
    addresses.forEach(address => {
        let addr = parseInt(address.address.replace(/[A-Za-z]/g, ''))
        let key = `dc_${addr}`
        let obj = {
            key: key,
            propertyName: address.propertyName,
            station: address.station,
            address: parseInt(address.address.replace(/[A-Za-z]/g, '')),
            type: address.type,
            length: 2,
            description: address.description
        }
        if (address.type === 'float') {
            lastValues[key] = 0.0
        } else if (address.type === 'int') {
            lastValues[key] = 0
        }
        baseAddressMap[obj.address] = obj
        baseAddressList.push(obj)
    })
}

async function initBaseAddress() {
    let base_config = await getConfig('plcConfig')
    let baseAddress = base_config.flowAddresses.WindingMachine
    let keys = Object.keys(baseAddress)
    keys.forEach(key => {
        //初始化地址的默认值
        let info = ADDRESS_INFO_MAP[key]
        if (info.type === 'string') {
            lastValues[key] = ''
        } else if (info.type === 'uint16') {
            lastValues[key] = 0
        }
        let obj = {
            key: key,
            address: parseInt(baseAddress[key].replace(/[A-Za-z]/g, '')),
            type: info.type,
            length: info.length,
            description: info.description
        }
        baseAddressMap[obj.address] = obj
        baseAddressList.push(obj)
    })
}

export async function startPlcService() {
    // if (true) {
    //     initAddress()
    //     return
    // }

    console.log('Start plcService...')
    //监控连接状态
    Promise.all([
        initAddress(),
        monitorConnectionStatus(),
        monitorDeviceStatus(),
        highSpeedPolling()
    ]).catch(error => {
        console.error('Start plcService error:', error.message)
    })
    console.log('Start plcService success!')
}


// 创建一个写入队列和锁
let writeQueue = []
let isWriting = false

export async function writeToPLC(address, value) {
    // 创建一个Promise，将其放入队列
    return new Promise((resolve, reject) => {
        writeQueue.push({address, value, resolve, reject})

        // 如果当前没有写入操作，启动处理队列
        if (!isWriting) {
            processWriteQueue()
        }
    })
}

// 创建延迟函数
const fn_delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))

// 处理写入队列的函数
async function processWriteQueue() {
    if (writeQueue.length === 0) {
        isWriting = false
        return
    }

    isWriting = true
    const {address, value, resolve, reject} = writeQueue.shift()

    try {
        console.log(`${currTime()}[写入 PLC] 正在将值 ${value} 写到 PLC 地址 ${address}`)
        // 添加超时机制
        const writeWithTimeout = Promise.race([
            await client.writeRegister(parseInt(address), value),
            new Promise((_, reject) =>
                setTimeout(() => reject(new Error('写入超时')), 3000) // 3秒超时
            )
        ])
        await writeWithTimeout
        resolve(true)
    } catch (error) {
        console.error(`[PLC 监控][写入 PLC] 写入 PLC 出错：${address}:${value}`, error)
        reject(error)
    } finally {
        // 继续处理队列中的下一个请求
        setTimeout(processWriteQueue, 0)
    }
}


function currTime() {
    //2025/02/08 08:32:05
    const now = new Date()
    return now.getFullYear() + '/' + String(now.getMonth() + 1).padStart(2, '0') + '/' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0')
}


